package com.atguigu.gmall.seckill.service.impl;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.feign.order.OrderFeignClient;
import com.google.common.collect.Lists;
import java.math.BigDecimal;
import java.util.Date;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.auth.UserAuthInfo;
import com.atguigu.gmall.common.execption.GmallException;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.utils.UserAuthUtils;
import com.atguigu.gmall.enums.OrderStatus;
import com.atguigu.gmall.enums.ProcessStatus;
import com.atguigu.gmall.order.entity.OrderDetail;
import com.atguigu.gmall.order.entity.OrderInfo;
import com.atguigu.gmall.seckill.dto.OrderDetailListDto;
import com.atguigu.gmall.seckill.dto.SeckillOrderSubmitDto;
import com.atguigu.gmall.seckill.dto.SeckillQueueMsg;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.gmall.seckill.entity.SeckillGoods;
import com.atguigu.gmall.seckill.service.SeckillGoodsService;
import com.atguigu.gmall.seckill.mapper.SeckillGoodsMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
* @author 34224
* @description 针对表【seckill_goods】的数据库操作Service实现
* @createDate 2023-11-29 09:44:22
*/
@Service
@Slf4j
public class SeckillGoodsServiceImpl extends ServiceImpl<SeckillGoodsMapper, SeckillGoods> implements SeckillGoodsService {

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper ;

    @Autowired
    private RedisTemplate<String , String> redisTemplate ;

    @Autowired
    private RabbitTemplate rabbitTemplate ;

    @Autowired
    private OrderFeignClient orderFeignClient ;

    private static final ConcurrentHashMap<Long , SeckillGoods> localCache = new ConcurrentHashMap<>() ;

    @Override
    public List<SeckillGoods> selectAllToDaySeckillGoods() {
        Date date = new Date() ;
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd") ;
        String format = simpleDateFormat.format(date);
        return seckillGoodsMapper.selectAllToDaySeckillGoods(format);
    }

    @Override
    public void saveDataToLocalCache(List<SeckillGoods> seckillGoods) {

        // 把本地缓存清空掉
        localCache.clear();

        // 把数据添加到本地缓存中
        seckillGoods.forEach(sg -> localCache.put(sg.getSkuId() , sg));

    }

    @Override
    public void saveDataToRedis(List<SeckillGoods> seckillGoods) {

        // 获取当前系统时间
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd") ;
        String format = simpleDateFormat.format(date);

        String redisKey = buildRedisKey();
        seckillGoods.forEach(sg -> {

            redisTemplate.opsForHash().put(redisKey , String.valueOf(sg.getSkuId()) , JSON.toJSONString(sg));

            // 把库存数据单独存储到Redis中
            String stockCountRedisKey = "seckill:stock:count:" + format + ":" + sg.getSkuId() ;
            redisTemplate.opsForValue().set(stockCountRedisKey , String.valueOf(sg.getStockCount() * 3 ) , 2 , TimeUnit.DAYS);

        });
        redisTemplate.expire(redisKey , 2 , TimeUnit.DAYS) ;

    }

    @Override
    public List<SeckillGoods> findAllSeckillGoods() {

        // 查询本地缓存
        ConcurrentHashMap<Long , SeckillGoods>  localCacheMap = localCache  ;
        if(localCacheMap.size() > 0) {
            Collection<SeckillGoods> seckillGoods = localCacheMap.values();
            List<SeckillGoods> goodsList = seckillGoods.stream().collect(Collectors.toList());
            log.info("从本地缓存中查询到了秒杀商品数据...");
            return goodsList ;
        }

        return syncRedisDataToMap() ;

    }

    public List<SeckillGoods> syncRedisDataToMap(){

        // 查询redis缓存
        String redisKey = buildRedisKey();
        List<Object> list = redisTemplate.opsForHash().values(redisKey);
        List<SeckillGoods> goodsList = list.stream().map(obj -> {
            String json = obj.toString();
            SeckillGoods seckillGoods = JSON.parseObject(json, SeckillGoods.class);
            return seckillGoods;
        }).collect(Collectors.toList());

        // 把redis中的数据存储到本地缓存中
        goodsList.stream().forEach(seckillGoods -> localCache.put(seckillGoods.getSkuId() , seckillGoods));
        log.info("从分布式缓存中查询到了秒杀商品数据,并且把秒杀商品数据存储到了本地缓存中...");

        return goodsList ;

    }

    @Override
    public SeckillGoods findBySkuId(Long skuId) {

        // 查询本地缓存
        SeckillGoods seckillGoods = localCache.get(skuId);
        if(seckillGoods != null) {
            log.info("从本地缓存中查询到了秒杀商品数据...");
            return seckillGoods ;
        }

        // 同步数据
        syncRedisDataToMap() ;

        // 查询数据
        return localCache.get(skuId);
    }

    @Override
    public String getSeckillSkuIdStr(Long skuId) {

        // 获取秒杀商品数据
        SeckillGoods seckillGoods = findBySkuId(skuId);

        // 获取秒杀商品的开始时间
        long startTime = seckillGoods.getStartTime().getTime();
        long currentTimeMillis = System.currentTimeMillis();
        if(startTime > currentTimeMillis) {
            throw new GmallException(ResultCodeEnum.SECKILL_NO_START) ;
        }

        // 获取秒杀商品的结束时间
        long endTime = seckillGoods.getEndTime().getTime();
        if(endTime < currentTimeMillis) {
            throw new GmallException(ResultCodeEnum.SECKILL_END) ;
        }

        // 判断剩余库存
        Integer stockCount = seckillGoods.getStockCount();
        if(stockCount <= 0) {
            throw new GmallException(ResultCodeEnum.SECKILL_FINISH) ;
        }

        // 生成秒杀码
        String seckillCode = genSeckillCode(skuId);

        // 存储秒杀码到Redis中
        String seckillCodeRedisKey = "seckill:goods:code:" + seckillCode ;
        Boolean hasKey = redisTemplate.hasKey(seckillCodeRedisKey);
        if(!hasKey) {
            redisTemplate.opsForValue().set(seckillCodeRedisKey , "0" , 1 , TimeUnit.DAYS);
        }

        return seckillCode;
    }

    @Override
    public void queue(Long skuId, String skuIdStr) {

        // 进行数据校验
        // 校验传递过来的秒杀码是否是当前登录用户这一天的秒杀码
        String seckillCode = genSeckillCode(skuId) ;
        if(!seckillCode.equals(skuIdStr)) {
            throw new GmallException(ResultCodeEnum.SECKILL_ILLEGAL) ;
        }

        // 校验是否是通过正常流程生成了秒杀码，正常流程在Redis中肯定会存在该秒杀码
        String seckillCodeRedisKey = "seckill:goods:code:" + skuIdStr ;
        Boolean hasKey = redisTemplate.hasKey(seckillCodeRedisKey);
        if(!hasKey) {
            throw new GmallException(ResultCodeEnum.SECKILL_ILLEGAL) ;
        }

        // 获取秒杀商品数据
        SeckillGoods seckillGoods = findBySkuId(skuId);

        // 获取秒杀商品的开始时间
        long startTime = seckillGoods.getStartTime().getTime();
        long currentTimeMillis = System.currentTimeMillis();
        if(startTime > currentTimeMillis) {
            throw new GmallException(ResultCodeEnum.SECKILL_NO_START) ;
        }

        // 获取秒杀商品的结束时间
        long endTime = seckillGoods.getEndTime().getTime();
        if(endTime < currentTimeMillis) {
            throw new GmallException(ResultCodeEnum.SECKILL_END) ;
        }

        // 判断剩余库存
        Integer stockCount = seckillGoods.getStockCount();
        if(stockCount <= 0) {
            throw new GmallException(ResultCodeEnum.SECKILL_FINISH) ;
        }

        // 判断之前是否已经发送过秒杀排队消息了
        Long increment = redisTemplate.opsForValue().increment(seckillCodeRedisKey);
        if(increment == 1) {            // 第一次发送

            // 进行库存的预扣减库存
            // 本地缓存的库存预扣减
            seckillGoods.setStockCount(seckillGoods.getStockCount() - 1);

            // redis缓存的库存预扣减
            // 获取当前系统时间
            Date date = new Date();
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd") ;
            String format = simpleDateFormat.format(date);

            // 把库存数据单独存储到Redis中
            String stockCountRedisKey = "seckill:stock:count:" + format + ":" + seckillGoods.getSkuId() ;
            Long decrement = redisTemplate.opsForValue().decrement(stockCountRedisKey);
            if(decrement < 0) {
                throw new GmallException(ResultCodeEnum.SECKILL_FINISH) ;
            }

            // 发生秒杀排队消息
            UserAuthInfo userAuthInfo = UserAuthUtils.getUserAuthInfo();
            String userId = userAuthInfo.getUserId();
            SeckillQueueMsg seckillQueueMsg = new SeckillQueueMsg(Long.parseLong(userId) , skuId , skuIdStr) ;
            rabbitTemplate.convertAndSend("seckill.exchange" , "seckill.queue" , JSON.toJSONString(seckillQueueMsg));

        }

    }

    @Override
    public ResultCodeEnum checkOrder(Long skuId) {

        // redis的key
        String seckillCode = genSeckillCode(skuId) ;
        String seckillOrderRedisKey = "seckill:goods:order:" + seckillCode ;

        // 从Redis中查询生成订单
        String json = redisTemplate.opsForValue().get(seckillOrderRedisKey);
        if(StringUtils.isEmpty(json)) {             //  排队中
            return ResultCodeEnum.SECKILL_RUN ;
        }else {
            if("x".equals(json)) {                  // 抢购失败
                return ResultCodeEnum.SECKILL_FINISH ;
            }else {
                OrderInfo orderInfo = JSON.parseObject(json, OrderInfo.class);
                if(orderInfo.getId() == null) {         // 抢购成功
                    return ResultCodeEnum.SECKILL_SUCCESS ;
                }else {
                    return ResultCodeEnum.SECKILL_ORDER_SUCCESS ;
                }
            }
        }

    }

    @Override
    public void deduceStock(Long skuId, int count) {

        // 根据skuId查询秒杀商品数据
        LambdaQueryWrapper<SeckillGoods> lambdaQueryWrapper = new LambdaQueryWrapper<>() ;
        lambdaQueryWrapper.eq(SeckillGoods::getSkuId , skuId) ;
        SeckillGoods seckillGoods = this.getOne(lambdaQueryWrapper);
        seckillGoods.setStockCount(seckillGoods.getStockCount() - count);

        // 更新数据库
        this.updateById(seckillGoods) ;

    }

    @Override
    public OrderInfo prepareOrderInfo(SeckillQueueMsg seckillQueueMsg) {

        // 根据skuId获取秒杀商品
        Long skuId = seckillQueueMsg.getSkuId();
        Long userId = seckillQueueMsg.getUserId();
        SeckillGoods seckillGoods = this.findBySkuId(skuId);

        // 构建订单对象，并且为其设置订单明细
        OrderInfo orderInfo = new OrderInfo() ;
        orderInfo.setTotalAmount(seckillGoods.getCostPrice());
        orderInfo.setOrderStatus(OrderStatus.UNPAID.name());
        orderInfo.setUserId(userId);
        orderInfo.setPaymentWay("online");
        String replace = UUID.randomUUID().toString().replace("-", "");
        orderInfo.setOutTradeNo(replace);
        orderInfo.setTradeBody(seckillGoods.getSkuName());
        orderInfo.setCreateTime(new Date());
        // long time = System.currentTimeMillis() + 1000 * 60 * 30 ;
        // orderInfo.setExpireTime(new Date(time));    // 按照真实订单的生成时间计算过期时间
        orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());
        orderInfo.setImgUrl(seckillGoods.getSkuDefaultImg());
        orderInfo.setOperateTime(new Date());
        orderInfo.setActivityReduceAmount(seckillGoods.getPrice().subtract(seckillGoods.getCostPrice()));
        orderInfo.setOriginalTotalAmount(seckillGoods.getPrice());

        // 构建明细数据
        OrderDetail orderDetail = new OrderDetail() ;
        orderDetail.setUserId(userId);
        orderDetail.setSkuId(skuId);
        orderDetail.setSkuName(seckillGoods.getSkuName());
        orderDetail.setImgUrl(seckillGoods.getSkuDefaultImg());
        orderDetail.setOrderPrice(seckillGoods.getCostPrice());
        orderDetail.setSkuNum("1");
        orderDetail.setCreateTime(new Date());
        orderDetail.setSplitTotalAmount(seckillGoods.getCostPrice());
        orderDetail.setSplitActivityAmount(seckillGoods.getPrice().subtract(seckillGoods.getCostPrice()));

        // 给订单设置明细数据
        orderInfo.setOrderDetailList(Arrays.asList(orderDetail));

        return orderInfo;
    }

    @Override
    public OrderInfo findOrderInfoBySkuId(Long skuId) {

        // 获取秒杀码
        String seckillCode = genSeckillCode(skuId) ;
        String orderInfoRedisKey = "seckill:goods:order:" + seckillCode ;

        // 从Redis中查询当前用户的临时订单数据
        String orderInfoJSON = redisTemplate.opsForValue().get(orderInfoRedisKey);

        // 返回
        return JSON.parseObject(orderInfoJSON , OrderInfo.class);
    }

    @Override
    public String submitOrder(SeckillOrderSubmitDto seckillOrderSubmitDto) {

        // 从Redis中获取临时订单
        OrderDetailListDto orderDetailListDto = seckillOrderSubmitDto.getOrderDetailList().get(0);
        Long skuId = orderDetailListDto.getSkuId();
        String seckillCode = genSeckillCode(skuId);
        String orderInfoRedisKey = "seckill:goods:order:" + seckillCode ;
        String orderInfoJSON = redisTemplate.opsForValue().get(orderInfoRedisKey);
        OrderInfo orderInfo = JSON.parseObject(orderInfoJSON, OrderInfo.class);

        // 给临时订单补全数据
        orderInfo.setConsignee(seckillOrderSubmitDto.getConsignee());
        orderInfo.setConsigneeTel(seckillOrderSubmitDto.getConsigneeTel());
        orderInfo.setDeliveryAddress(seckillOrderSubmitDto.getDeliveryAddress());
        orderInfo.setOrderComment(seckillOrderSubmitDto.getDeliveryAddress());
        long time = System.currentTimeMillis() + 1000 * 60 * 30 ;
        orderInfo.setExpireTime(new Date(time));    // 按照真实订单的生成时间计算过期时间

        // 远程调用service-order微服务的接口保存订单数据
        Result<String> orderIdResult = orderFeignClient.saveOrderInfo(orderInfo) ;
        String orderId = orderIdResult.getData();

        // 把订单数据重新保存到Redis中
        orderInfo.setId(Long.parseLong(orderId));
        redisTemplate.opsForValue().set(orderInfoRedisKey , JSON.toJSONString(orderInfo) , 2 , TimeUnit.DAYS);

        // 返回订单的id
        return orderId;
    }

    public String genSeckillCode(Long skuId) {

        // 获取当前登录用户的id
        UserAuthInfo userAuthInfo = UserAuthUtils.getUserAuthInfo();
        String userId = userAuthInfo.getUserId();

        // 获取当前系统时间
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd") ;
        String format = simpleDateFormat.format(date);

        // 拼接秒杀码
        String seckillCodeStr = format + "-" + userId + "-" + skuId ;

        // 返回
        String digest = DigestUtils.md5DigestAsHex(seckillCodeStr.getBytes());
        return digest ;

    }

    public String buildRedisKey() {
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd") ;
        String format = simpleDateFormat.format(date);
        String redisKey = "seckill:goods:" + format;
        return redisKey ;
    }

}




